/*
* 
* Practica_02_base.js
* Videojuegos (30262) - Curso 2019-2020
* 
* Parte adaptada de: Alex Clarke, 2016, y Ed Angel, 2015.
* 
*/

// Variable to store the WebGL rendering context
var gl;

//----------------------------------------------------------------------------
// OTHER DATA 
//----------------------------------------------------------------------------

var model = new mat4();   		// create a model matrix and set it to the identity matrix
var view = new mat4();   		// create a view matrix and set it to the identity matrix
var projection = new mat4();	// create a projection matrix and set it to the identity matrix

var eye, target, up;			// for view matrix

var planeProgramInfo = {
	program: {},
	uniformLocations: {},
	attribLocations: {},
};

var sphereProgramInfo = {
	program: {},
	uniformLocations: {},
	attribLocations: {},
};

// List of objects to draw
var objectsToDraw = [
	{
		name: "plane",
		programInfo: planeProgramInfo,
		pointsArray: pointsPlane, 
		uniforms: {
			u_color: [0.0, 0.5, 0.1, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "plane",
		programInfo: planeProgramInfo,
		pointsArray: pointsPlane2, 
		uniforms: {
			u_color: [0.5, 0.25, 0.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "plane",
		programInfo: planeProgramInfo,
		pointsArray: pointsPlane3, 
		uniforms: {
			u_color: [0.5, 0.25, 0.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "plane",
		programInfo: planeProgramInfo,
		pointsArray: pointsPlane4, 
		uniforms: {
			u_color: [0.5, 0.25, 0.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "plane",
		programInfo: planeProgramInfo,
		pointsArray: pointsPlane5, 
		uniforms: {
			u_color: [0.5, 0.25, 0.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [1.0, 1.0, 1.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [0.1, 0.1, 0.3, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [0.5, 1.0, 0.5, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [0.5, 0.5, 1.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [1.0, 1.0, 0.5, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [1.0, 0.5, 1.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [0.5, 1.0, 1.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
	{
		name: "sphere",
		programInfo: sphereProgramInfo,
		pointsArray: pointsSphere, 
		uniforms: {
			u_color: [1.0, 0.5, 0.0, 1.0],
			u_model: new mat4(),
			position: vec3(0.0, 0.0, 0.0), // initial position of sphere
			velocity: vec3(0.0, 0.0, 0.0),  // initial velocity of sphere
		},
		primType: "triangles",
	},
];

// List of spheres with their current state
var spheres = [
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.0005, 0.0005, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.0005, 0.0005, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.0005, 0.0005, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.001, 0.001, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.001, 0.001, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.001, 0.001, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.001, 0.001, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
	{
		position: [0.0, 0.0, 0.0],
		rotation: [0.0, 0.0, 0.0],
		velocity: [0.0, 0.0, 0.0],
		acceleration: [0.001, 0.001, 0.0],
		diametro: 1.5,
		absorption: 0.75,
		mass: 1.0,
		xDir: 0,
		yDir: 0,
		update: true,
	},
]

var iniciado = false;

//----------------------------------------------------------------------------
// Initialization function
//----------------------------------------------------------------------------

window.onload = function init() {
	
	// Set up a WebGL Rendering Context in an HTML5 Canvas
	var canvas = document.getElementById("gl-canvas");
	gl = WebGLUtils.setupWebGL(canvas);
	if (!gl) {
		alert("WebGL isn't available");
	}

	//  Configure WebGL
	gl.clearColor(0.0, 0.0, 0.0, 1.0);
	gl.enable(gl.DEPTH_TEST);
	gl.disable(gl.CULL_FACE);

	setPrimitive(objectsToDraw);

	// Set up a WebGL program for the plane
	// Load shaders and initialize attribute buffers
	planeProgramInfo.program = initShaders(gl, "plane-vertex-shader", "plane-fragment-shader");
	  
	// Save the attribute and uniform locations
	planeProgramInfo.uniformLocations.model = gl.getUniformLocation(planeProgramInfo.program, "model");
	planeProgramInfo.uniformLocations.view = gl.getUniformLocation(planeProgramInfo.program, "view");
	planeProgramInfo.uniformLocations.projection = gl.getUniformLocation(planeProgramInfo.program, "projection");
	planeProgramInfo.uniformLocations.baseColor = gl.getUniformLocation(planeProgramInfo.program, "baseColor");
	planeProgramInfo.attribLocations.vPosition = gl.getAttribLocation(planeProgramInfo.program, "vPosition");

	// Set up a WebGL program for spheres
	// Load shaders and initialize attribute buffers
	sphereProgramInfo.program = initShaders(gl, "sphere-vertex-shader", "sphere-fragment-shader");
	  
	// Save the attribute and uniform locations
	sphereProgramInfo.uniformLocations.model = gl.getUniformLocation(sphereProgramInfo.program, "model");
	sphereProgramInfo.uniformLocations.view = gl.getUniformLocation(sphereProgramInfo.program, "view");
	sphereProgramInfo.uniformLocations.projection = gl.getUniformLocation(sphereProgramInfo.program, "projection");
	sphereProgramInfo.uniformLocations.baseColor = gl.getUniformLocation(sphereProgramInfo.program, "baseColor");
	sphereProgramInfo.attribLocations.vPosition = gl.getAttribLocation(sphereProgramInfo.program, "vPosition");

	// Set up viewport 
	gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

	// Set initial positions

	spheres[0].position = [0.0, 0.0, spheres[0].diametro/2];
	spheres.forEach(function(sphere, index) {
		if(index != 0){
			sphere.position = [5*(2*Math.random() - 1), 5*(2*Math.random() - 1), sphere.diametro/2];
			i=0
			for(i; i<index; i++){
				if(distance(sphere.position, spheres[i].position) < sphere.diametro){
					sphere.position = [13*(2*Math.random() - 1), 8*(2*Math.random() - 1), sphere.diametro/2];
					i = -1;
				}
			}
			sphere.rotation = [Math.random()*360, Math.random()*360, Math.random()*360];
		}
	});
	
	lastTick = Date.now();
	requestAnimFrame(tick);
};

//----------------------------------------------------------------------------
// Tick Event Function
//----------------------------------------------------------------------------

var lastTick;

function tick(nowish) {
	var now = Date.now();

    var dt = now - lastTick;
    lastTick = now;

	render(dt)
	update(dt)

	window.requestAnimationFrame(tick)
}

let keys_pressed = {}; // Mapa para guardar las teclas presionadas.
document.addEventListener('keydown', (event) => {
	// Si no se presiona ninguna de las teclas utilizadas en el programa, no se meten.
	
	if (event.key == 'a' || event.key == 'A' || event.key == 'd' || event.key == 'D' || event.key == ' ')
		keys_pressed[event.key] = true;
});

document.addEventListener('keyup', (event) => {
    keys_pressed[event.key] = false;
});

let prevMouseX = null;
let prevMouseY = null;
let mouseDist = 0;
let mouseDown = false;
let moving = false;
var iniciando = true;
var fin = false;
// Agregar un controlador de eventos para el botón del ratón.
document.addEventListener('mousedown', function(event) {
	if(!moving && iniciado) {
		mouseDown = true;
		prevMouseX = event.clientX;
		prevMouseY = event.clientY;
	}
});

document.addEventListener('mousemove', function(event) {
	if(mouseDown && iniciado) {
		var powerLevel = document.getElementById('powerLevel');
		const deltaX = prevMouseX - event.clientX;
		const deltaY = prevMouseY - event.clientY;
		powerLevel.style.height = Math.min((Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 400 ) * 100, 100) + '%';
	}
});

// Agregar un controlador de eventos para soltar el botón del ratón.
document.addEventListener('mouseup', function(event) {
	if(!moving && iniciado) {
		mouseDown = false;
		// Mostrar la distancia recorrida por el ratón.
		const deltaX = prevMouseX - event.clientX;
		const deltaY = prevMouseY - event.clientY;
		mouseDist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
	}
});

var angle = 3*Math.PI/2;

var cameraParameters = { direction:[0, 0, 0], terminalVelocity:1, acceleration:0.0001 }

function camera() {
	cameraParameters.direction = [0, 0, 0];

	if (keys_pressed['a'] || keys_pressed['A']) angle = angle < 0 ? angle + 2*Math.PI : angle - 0.04;

	if (keys_pressed['d'] || keys_pressed['D']) angle = angle > 2*Math.PI ? angle - 2*Math.PI : angle + 0.04;
	
}



//----------------------------------------------------------------------------
// Math functions
//----------------------------------------------------------------------------

function clamp(num, min, max) {
	return Math.min(Math.max(num, min), max);
}

function vec_clamp_k(v, min, max) {
	return v.map((x, i) => clamp(x, min, max));
}

function vectorDistance(a, b) {
    var dist = [Math.abs(b[0]-a[0]), Math.abs(b[1]-a[1]), Math.abs(b[2]-a[2])];
    return dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2];
}

function distance(a, b) {
    return Math.sqrt(vectorDistance(a,b));
}

function modulo(v) {
    return Math.sqrt(v.map((x, i) => v[i] * v[i]).reduce((m, n) => m + n));
}

function normalizacion(v) {
    return division_entero(v, modulo(v));
}

function dot(a, b) {
	return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
    //return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n);
}

function negar(v) {
    return [-v[0], -v[1], -v[2]];
}

function suma(a, b) {
    return [a[0]+b[0], a[1]+b[1], a[2]+b[2]];
}

function resta(a,b) {
    return [a[0]-b[0], a[1]-b[1], a[2]-b[2]];
}

function division_entero(v, k) {
    return [v[0]/k, v[1]/k, v[2]/k];
}

function multiplicacion_entero(v,k) {
    return [v[0]*k, v[1]*k, v[2]*k];
}

function corregirColision(sphere1, sphere2) {
	const distanciaX = sphere2.position[0] - sphere1.position[0];
	const distanciaY = sphere2.position[1] - sphere1.position[1];
	const distanciaTotal = Math.sqrt(distanciaX ** 2 + distanciaY ** 2);
  
	const superposicion = (sphere1.diametro/2.0 + sphere2.diametro/2.0) - distanciaTotal;
	const direccionX = distanciaX / distanciaTotal;
	const direccionY = distanciaY / distanciaTotal;
  
	const correccionX = direccionX * superposicion * 0.5;
	const correccionY = direccionY * superposicion * 0.5;
  
	sphere1.position[0] -= correccionX;
	sphere1.position[1] -= correccionY;
	sphere2.position[0] += correccionX;
	sphere2.position[1] += correccionY;
}

function interseccion(sphere1, sphere2) {
    let vec = resta(suma(sphere2.position, sphere2.velocity), suma(sphere1.position, sphere1.velocity))
    if(dot(vec, vec) <= (sphere1.diametro * sphere1.diametro)){
		//corregirColision(sphere1, sphere2);
		return true;
	}
	return false;
}

function reducirVelocidades(index){
	if((spheres[index].velocity[0] != 0) || (spheres[index].velocity[1] != 0)){
		//console.log('Bola ', index);
		//console.log('\tFreno bola ', index);
		//console.log('\tAntes: ', spheres[index].velocity);
		//console.log('\tAceleración: ', spheres[index].acceleration);
		//console.log('\txdir: ', spheres[index].xDir);
		//console.log('\tydir: ', spheres[index].yDir);
	}
	let relx = 0.3; //1;
	let rely = 0.3; //1;
	if((spheres[index].velocity[0] != 0) && (spheres[index].velocity[1] != 0)) {
		let v0 = Math.abs(spheres[index].velocity[0]);
		let v1 = Math.abs(spheres[index].velocity[1]);
		if(v0 > v1){
			rely = v1 / v0;
		} else {
			relx = v0 / v1;
		}
	}

	if(spheres[index].xDir == 1) {
		spheres[index].velocity[1] -= (spheres[index].acceleration[1] * rely);
	}
	else if(spheres[index].xDir == -1) {
		spheres[index].velocity[1] += (spheres[index].acceleration[1] * rely);
	}
	
	if(spheres[index].yDir == 1){
	 	spheres[index].velocity[0] -= (spheres[index].acceleration[0] * relx);
	}
	else if(spheres[index].yDir == -1) {
		spheres[index].velocity[0] += (spheres[index].acceleration[0] * relx);
	}
	
	if(((spheres[index].velocity[0] < 0) && (spheres[index].yDir == 1)) || ((spheres[index].velocity[0] > 0) &&  (spheres[index].yDir == -1))) {
		spheres[index].velocity[0] = 0;
		yDir = 0;
	}
	if(((spheres[index].velocity[1] < 0.0) &&  (spheres[index].xDir == 1)) || ((spheres[index].velocity[1] > 0.0) &&  (spheres[index].xDir == -1))) {
		spheres[index].velocity[1] = 0;
		xDir = 0;
	}
	if((spheres[index].velocity[0] != 0.0) || (spheres[index].velocity[1] != 0.0)){
		//console.log('\trelx: ', relx);
		//console.log('\trely: ', rely);
		//console.log('\tDespués: ', spheres[index].velocity);
	}
}
function caida(index){
	let sphere = spheres[index];
	var fall = false;
	if(sphere.position[0] < -14 && sphere.position[1] < -9){
		sphere.position[2] -= gravity; 
		fall = true;
	}else if(sphere.position[0] < -14 && sphere.position[1] > 9){
		sphere.position[2] -= gravity; 
		fall = true;
	}else if(sphere.position[0] > 14 && sphere.position[1] < -9){
		sphere.position[2] -= gravity;
		fall = true;
	}else if(sphere.position[0] > 14 && sphere.position[1] > 9){
		sphere.position[2] -= gravity; 
		fall = true;
	}else if((sphere.position[0] > -0.5 && sphere.position[0] < 0.5) && sphere.position[1] < -9){
		sphere.position[2] -= gravity; 
		fall = true;
	}else if((sphere.position[0] > -0.5 && sphere.position[0] < 0.5) && sphere.position[1] > 9){
		sphere.position[2] -= gravity; 
		fall = true;
	}else{
		sphere.position[2] = sphere.diametro/2;
	}

	if(fall){
		sphere.velocity[0] = 0;
		sphere.velocity[1] = 0;
	}
	
	if((index == 0) && (sphere.position[2] < -1.5)){
		sphere.position = [13*(2*Math.random() - 1), 8*(2*Math.random() - 1), sphere.diametro/2];
		var i = 1;
		for(i; i<spheres.length; i++){
			if(distance(sphere.position, spheres[i].position) < sphere.diametro){
				sphere.position = [5*(2*Math.random() - 1), 5*(2*Math.random() - 1), sphere.diametro/2];
				i = -1;
			}
		}
		sphere.rotation = [Math.random()*360, Math.random()*360, Math.random()*360];
			
	}else if(sphere.position[2] < -1.5 && index != 0){
		sphere.update = false;
	}
	sphere[index] = sphere;
}

function calcularAngulo(v1, v2){
	/*if((v1[0] == 0.0) && (v1[0] == 0.0) && (v1[0] == 0.0)) {

	}*/
	let coseno = dot(v1,v2) / (modulo(v1) * modulo(v2));
	return Math.acos(coseno);
}

function colisionBolasBolas(index){
	var i = index + 1;
	for(i; i < spheres.length; i++) {
		let sphere = spheres[index];
		let otherSphere = spheres[i];
		if (interseccion(sphere, otherSphere)) {
			var angulo_colision = 0.0;
			var quieta = false;
			if(sphere.velocity[0] == 0 && sphere.velocity[1] == 0) {
				quieta = true;
				angulo_colision = calcularAngulo(otherSphere.velocity, resta(sphere.position, otherSphere.position));
				sphere = spheres[i];
				otherSphere = spheres[index];
			} else { 
				angulo_colision = calcularAngulo(sphere.velocity, resta(otherSphere.position, sphere.position));
			}
			console.log('Angulo ', (angulo_colision * 180) / Math.PI);
			corregirColision(sphere, otherSphere);

			console.log('v1_inicial', sphere.velocity);
			console.log('v2_inicial', otherSphere.velocity);
			const masa1 = sphere.mass;
			const masa2 = otherSphere.mass;
			
			// Descomponer las velocidades iniciales en las componentes paralela y perpendicular al ángulo de colisión	
			const v1_inicial_x = sphere.velocity[0];
			const v1_inicial_y = sphere.velocity[1];
			const v2_inicial_x = otherSphere.velocity[0];
			const v2_inicial_y = otherSphere.velocity[1];
			
			// Calcular las componentes paralelas y perpendiculares al ángulo de colisión
			const v1_paralela = v1_inicial_x * Math.cos(angulo_colision) + v1_inicial_y * Math.sin(angulo_colision);
			const v1_perpendicular = -v1_inicial_x * Math.sin(angulo_colision) + v1_inicial_y * Math.cos(angulo_colision);
			const v2_paralela = v2_inicial_x * Math.cos(angulo_colision) + v2_inicial_y * Math.sin(angulo_colision);
			const v2_perpendicular = -v2_inicial_x * Math.sin(angulo_colision) + v2_inicial_y * Math.cos(angulo_colision);
			
			// Calcular las velocidades finales paralela y perpendicular por separado
			const v1_paralela_final = ((masa1 - masa2) * v1_paralela + 2 * masa2 * v2_paralela) / (masa1 + masa2);
			const v2_paralela_final = ((masa2 - masa1) * v2_paralela + 2 * masa1 * v1_paralela) / (masa1 + masa2);
			//console.log('v1_paralela', v1_paralela);
			//console.log('v2_paralela', v2_paralela);
			//console.log('v1_paralela_final', v1_paralela_final);
			//console.log('v2_paralela_final', v2_paralela_final);
			
			// Mantener la velocidad perpendicular constante después de la colisión
			const v1_perpendicular_final = v1_perpendicular;
			const v2_perpendicular_final = v2_perpendicular;
			//console.log('v1_perpendicular_final', v1_perpendicular_final);
			//console.log('v2_perpendicular_final', v2_perpendicular_final);
			
			// Calcular las velocidades finales totales
			sphere.velocity[0] = v1_paralela_final * Math.cos(angulo_colision) - v1_perpendicular_final * Math.sin(angulo_colision);
			sphere.velocity[1] = v1_paralela_final * Math.sin(angulo_colision) + v1_perpendicular_final * Math.cos(angulo_colision);
			otherSphere.velocity[0] = v2_paralela_final * Math.cos(angulo_colision) - v2_perpendicular_final * Math.sin(angulo_colision);
			otherSphere.velocity[1] = v2_paralela_final * Math.sin(angulo_colision) + v2_perpendicular_final * Math.cos(angulo_colision);

			if(sphere.velocity[0] > 0) sphere.yDir = 1;
			else if(sphere.velocity[0] < 0) sphere.yDir = -1

			if(sphere.velocity[1] > 0) sphere.xDir = 1;
			else if(sphere.velocity[1] < 0) sphere.xDir = -1

			if(otherSphere.velocity[0] > 0) otherSphere.yDir = 1;
			else if(otherSphere.velocity[0] < 0) otherSphere.yDir = -1

			if(otherSphere.velocity[1] > 0) otherSphere.xDir = 1;
			else if(otherSphere.velocity[1] < 0) otherSphere.xDir = -1
		
			console.log('v1_final', sphere.velocity);
			console.log('v2_final', otherSphere.velocity);
		}

		if(quieta) {
			spheres[i] = sphere;
			spheres[index] = otherSphere;
		} else {
			spheres[i] = otherSphere;
			spheres[index] = sphere;
		}
	}
}

function colisionBolasPared(index){
	let sphere = spheres[index];
	var radio = sphere.diametro/2
	if(sphere.position[0] + radio >= 15){
		sphere.velocity[0] = -sphere.velocity[0]*0.75;
		sphere.position[0] = 15 - radio;
		sphere.yDir = -sphere.yDir;
	}
	if(sphere.position[0] - radio <= -15){
		sphere.velocity[0] = -sphere.velocity[0]*0.75;
		sphere.position[0] = -15 + radio;
		sphere.yDir = -sphere.yDir;
	}
	if(sphere.position[1] + radio >= 10){
		sphere.velocity[1] = -sphere.velocity[1]*0.75;
		sphere.position[1] = 10 - radio;
		sphere.xDir = -sphere.xDir;
	}
	if(sphere.position[1] - radio <= -10){
		sphere.velocity[1] = -sphere.velocity[1]*0.75;
		sphere.position[1] = -10 + radio;
		sphere.xDir = -sphere.xDir;
	}
	spheres[index] = sphere;
}

//----------------------------------------------------------------------------
// Update Event Function 
//----------------------------------------------------------------------------
const gravity = 0.1
const air_friction = 1; //0.01
var start_time = 2999;
var play_time = 0.0;
var fin_text = "";
var mejor_tiempo = 0.0;

function update(dt) {

	// Update state
	var sumUpdate = 0;
	if(iniciado) {
		const playTimeText = document.getElementById('playTime');
		playTimeText.textContent = (play_time / 1000).toFixed(1);
		if(spheres[0].velocity[0] == 0.0 && spheres[0].velocity[1] == 0.0 && spheres[0].velocity[2] == 0.0) {
			moving = false;
			if(!mouseDown) {
				var powerLevel = document.getElementById('powerLevel');
				powerLevel.style.height = '0%';
			}
		} else {
			moving = true;
		}
		play_time += dt;
		spheres.forEach(function(sphere, index) {
			let radio = sphere.diametro / 2.0;
			if(sphere.update){
				// Update state (rotation) of the sphere
				sphere.rotation[0] += Math.atan2(sphere.velocity[1], (2 * Math.PI * radio)) * 70 * dt;
				sphere.rotation[1] += Math.atan2(sphere.velocity[0], (2 * Math.PI * radio)) * 70 * dt;
				
				sphere.position[0] += sphere.velocity[0] * dt;
				sphere.position[1] += sphere.velocity[1] * dt;

				colisionBolasBolas(index);
				colisionBolasPared(index);

				reducirVelocidades(index);

				caida(index);
				
				
				let transform = scale(sphere.diametro, sphere.diametro, sphere.diametro);

				let ejeX = vec3(-1.0, 0.0, 0.0);
				transform = mult(transform, rotate(sphere.rotation[0], ejeX));
				let ejeY = vec3(0.0, 1.0, 0.0);
				transform = mult(transform, rotate(sphere.rotation[1], ejeY));
				let ejeZ = vec3(0.0, 0.0, 1.0);
				transform = mult(transform, rotate(sphere.rotation[2], ejeZ));

				transform = mult(translate(sphere.position[0], sphere.position[1], sphere.position[2]), transform);
				
				// Skip the plane
				index += 5;

				objectsToDraw[index].uniforms.u_model = transform;
			}else sumUpdate += 1;
		});
	} else if (iniciando) {
		const textContainer = document.getElementById('countDown');
		if(start_time <= 0) {
			iniciado = true;
			iniciando = false;
			start_time = 2999;
			textContainer.textContent = '';
		} else {
			start_time -= dt;
			var time = Math.trunc(start_time / 1000) + 1;
	
			// Modify the text inside the container
			textContainer.textContent = time;
		}
	} else if (fin) {
		if(keys_pressed[' ']) {
			const winOrLoseText = document.getElementById('winOrLose');
			winOrLoseText.textContent = '';
			const gameTimeText = document.getElementById('gameTime');
			gameTimeText.textContent = '';
			const bestTimeText = document.getElementById('bestTime');
			bestTimeText.textContent = '';
			const brecordText = document.getElementById('record');
			brecordText.textContent = '';
			const restartText = document.getElementById('restart');
			restartText.textContent = '';
			spheres.forEach(function (sphere, index) {
				sphere.velocity[0] = 0.0;
				sphere.velocity[1] = 0.0;
				sphere.velocity[2] = 0.0;
				sphere.position = [13*(2*Math.random() - 1), 8*(2*Math.random() - 1), sphere.diametro/2];
				var i = 0;
				for(i; i<index; i++){
					if(index != i){
						if(distance(sphere.position, spheres[i].position) < sphere.diametro){
							sphere.position = [5*(2*Math.random() - 1), 5*(2*Math.random() - 1), sphere.diametro/2];
							i = -1;
						}
					}
				}
				sphere.rotation = [Math.random()*360, Math.random()*360, Math.random()*360];
				sphere.update = true;		
				
			});
			fin = false;
			iniciando = true;
		}
		
	}

	if((!spheres[1].update) && iniciado) { // Derrota
		iniciado = false;
		fin = true;
		play_time = 0.0;
		const textContainer = document.getElementById('winOrLose');
		textContainer.textContent = 'Has perdido';
		const restartText = document.getElementById('restart');
		restartText.textContent = 'Pulsa la barra espaciadora para volver a jugar';
	} else if((sumUpdate == spheres.length-2) && iniciado) { // Victoria
		iniciado = false;
		fin = true;
		const winOrLoseText = document.getElementById('winOrLose');
		winOrLoseText.textContent = '¡Victoria!';
		const gameTimeText = document.getElementById('gameTime');
		gameTimeText.textContent = 'La partida ha durado ' + (play_time / 1000).toFixed(1) + ' segundos';
		const bestTimeText = document.getElementById('bestTime');
		if(mejor_tiempo == 0.0) {
			bestTimeText.textContent = 'Mejor tiempo: - segundos';
		} else {
			bestTimeText.textContent = 'Mejor tiempo: ' + (mejor_tiempo / 1000).toFixed(1) + ' segundos';
		}
		if((play_time <= mejor_tiempo) || (mejor_tiempo == 0.0)) {
			const brecordText = document.getElementById('record');
			brecordText.textContent = '¡¡NUEVO RÉCORD!!';
			mejor_tiempo = play_time;
		}
		const restartText = document.getElementById('restart');
		restartText.textContent = 'Pulsa la barra espaciadora para volver a jugar';
		play_time = 0.0;
	}
}

//----------------------------------------------------------------------------
// Rendering Event Function
//----------------------------------------------------------------------------

function render(dt) {
	// Clear the buffer and draw everything
	gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);

	if(iniciado) {
		objectsToDraw.forEach(function(object) {
			gl.useProgram(object.programInfo.program);

			// Setup buffers and attributes
			setBuffersAndAttributes(object.programInfo, object);

			// Set the uniforms
			setUniforms(object.programInfo, object.uniforms);

			// Draw
			gl.drawArrays(object.primitive, 0, object.pointsArray.length);
		});
	}

	camera();
}

//----------------------------------------------------------------------------
// Utils functions
//----------------------------------------------------------------------------

function setPrimitive(objectsToDraw) {	
	
	objectsToDraw.forEach(function(object) {
		switch(object.primType) {
		  case "lines":
			object.primitive = gl.LINES;
			break;
		  case "line_strip":
			object.primitive = gl.LINE_STRIP;
			break;
		  case "triangles":
			object.primitive = gl.TRIANGLES;
			break;
		  default:
			object.primitive = gl.TRIANGLES;
		}
	});	
}	

function setUniforms(pInfo, uniforms) {
	var canvas = document.getElementById("gl-canvas");

	// Set up camera
	// Projection matrix
	projection = perspective( 45.0, canvas.width/canvas.height, 0.1, 100.0 );																		
	gl.uniformMatrix4fv( pInfo.uniformLocations.projection, gl.FALSE, projection ); // copy projection to uniform value in shader

	// View matrix (static cam)
	eye = vec3(spheres[0].position[0] + 10*Math.cos(angle), spheres[0].position[1] + 10*Math.sin(angle), 8.0);
	target = vec3(spheres[0].position[0], spheres[0].position[1], spheres[0].position[2]);
	up = vec3(0.0, 0.0, 1.0);
	view = lookAt(eye,target,up);
	
	if (iniciado && mouseDist > 0.0 && spheres[0].velocity[0] == 0 && spheres[0].velocity[1] == 0 && spheres[0].velocity[2] == 0) {
		let shot_dir = normalizacion(subtract(target, eye));
		let shot_speed = Math.min(0.2, (mouseDist * 0.2) / 400);
		
		spheres[0].velocity[0] = shot_dir[0] * shot_speed;
		spheres[0].velocity[1] = shot_dir[1] * shot_speed;
		mouseDist = 0.0;
		
		if(spheres[0].velocity[0] > 0) spheres[0].yDir = 1;
		if(spheres[0].velocity[0] < 0) spheres[0].yDir = -1;
		if(spheres[0].velocity[1] > 0) spheres[0].xDir = 1;
		if(spheres[0].velocity[1] < 0) spheres[0].xDir = -1;
	}
	
	gl.uniformMatrix4fv(pInfo.uniformLocations.view, gl.FALSE, view); // copy view to uniform value in shader

	// Copy uniform model values to corresponding values in shaders
	//if (pInfo.uniformLocations.baseColor != null) {
		gl.uniform4f(pInfo.uniformLocations.baseColor, uniforms.u_color[0], uniforms.u_color[1], uniforms.u_color[2], uniforms.u_color[3]);
	//}
	gl.uniformMatrix4fv(pInfo.uniformLocations.model, gl.FALSE, uniforms.u_model);
}

function setBuffersAndAttributes(pInfo, object) {
	// Load the data into GPU data buffers
	// Vertices
	var vertexBuffer = gl.createBuffer();
	gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );
	gl.bufferData( gl.ARRAY_BUFFER,  flatten(object.pointsArray), gl.STATIC_DRAW );
	gl.vertexAttribPointer( pInfo.attribLocations.vPosition, 4, gl.FLOAT, gl.FALSE, 0, 0 );
	gl.enableVertexAttribArray( pInfo.attribLocations.vPosition );

	// Colors
	if (object.colorsArray != null) {
		
		var colorBuffer = gl.createBuffer();
		gl.bindBuffer( gl.ARRAY_BUFFER, colorBuffer );
		gl.bufferData( gl.ARRAY_BUFFER,  flatten( object.colorsArray), gl.STATIC_DRAW );
		gl.vertexAttribPointer( pInfo.attribLocations.vColor, 4, gl.FLOAT, gl.FALSE, 0, 0 );
		gl.enableVertexAttribArray( pInfo.attribLocations.vColor );
	}
}
